﻿using System;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Threading.Tasks;

namespace GeneralTool.CoreLibrary.SocketLib
{
    /// <summary>
    /// Socket扩展类
    /// </summary>
    public static class SocketExtensions
    {
        #region Public 方法

        /// <summary>
        /// 确定当前Socket是否还处于连接状态
        /// </summary>
        /// <param name="socket">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool IsClientConnected(this Socket socket, string localEndPoint, string remoteEndPoint)
        {
            if (socket == null || !socket.Connected)
                return false;

            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();

            TcpConnectionInformation[] tcpConnections = ipProperties.GetActiveTcpConnections();

            foreach (TcpConnectionInformation c in tcpConnections)
            {
                TcpState stateOfConnection = c.State;

                try
                {
                    if ((c.LocalEndPoint + "").Equals(localEndPoint) && (c.RemoteEndPoint + "").Equals(remoteEndPoint))
                    {

                        /* 项目“GeneralTool.CoreLibrary (net452)”的未合并的更改
                        在此之前:
                                                if (stateOfConnection == TcpState.Closed || stateOfConnection == TcpState.CloseWait)
                                                {
                                                    return false;
                                                }
                                                else
                                                {
                                                    return true;
                                                }
                                            }
                        在此之后:
                                                if (stateOfConnection != TcpState.Closed && stateOfConnection != TcpState.CloseWait;
                                            }
                        */
                        return stateOfConnection != TcpState.Closed && stateOfConnection != TcpState.CloseWait;
                    }
                }
                catch
                {
                    return false;
                }
            }

            return false;
        }

        /// <summary>
        /// 设置Socket为长连接
        /// </summary>
        /// <param name="socket">
        /// </param>
        public static void SetSocketKeepAlive(this Socket socket)
        {
            uint dummy = 0;
            byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
            BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
            BitConverter.GetBytes((uint)3000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//keep-alive间隔
            BitConverter.GetBytes((uint)500).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);// 尝试间隔

            _ = socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
        }

        /// <summary>
        /// 读取指定数量的数据
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="dataLength"></param>
        /// <param name="timeOutMillSeconds"></param>
        /// <returns></returns>
        /// <exception cref="TimeoutException"></exception>
        public static byte[] ReadBytes(this Socket socket, int dataLength, int timeOutMillSeconds = 0)
        {
            // 用于存储接收到的数据
            byte[] dataBuffer = new byte[dataLength];
            int totalBytesRead = 0;
            int bytesRead = 0;
            var time = DateTime.Now;
            // 循环接收数据，直到接收到指定长度的数据
            while (true)
            {
                bytesRead = socket.Receive(dataBuffer, totalBytesRead, dataLength - totalBytesRead, SocketFlags.None);
                if (bytesRead == 0)
                {
                    break;
                }

                if (timeOutMillSeconds > 0)
                {
                    var curr = DateTime.Now;
                    var span = curr - time;
                    if (span.TotalMilliseconds >= timeOutMillSeconds)
                        throw new TimeoutException("等待服务端返回超时");
                }

                totalBytesRead += bytesRead;
                if (totalBytesRead == dataLength)
                    break;
            }
            return dataBuffer;
        }

        /// <summary>
        /// 获取流数据
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="dataLength">要获取的长度</param>
        /// <param name="timeOutMillSeconds">超时时间</param>
        /// <returns></returns>
        /// <exception cref="TimeoutException"></exception>
        public static byte[] ReadBytes(this NetworkStream stream, int dataLength, int timeOutMillSeconds = 0)
        {
            // 用于存储接收到的数据
            byte[] dataBuffer = new byte[dataLength];
            int totalBytesRead = 0;
            int bytesRead = 0;
            var time = DateTime.Now;
            // 循环接收数据，直到接收到指定长度的数据
            while (true)
            {
                bytesRead = stream.Read(dataBuffer, totalBytesRead, dataLength - totalBytesRead);
                if (bytesRead == 0)
                {
                    break;
                }

                if (timeOutMillSeconds > 0)
                {
                    var curr = DateTime.Now;
                    var span = curr - time;
                    if (span.TotalMilliseconds >= timeOutMillSeconds)
                    {
                        //stream.Close();
                        throw new TimeoutException("等待服务端返回超时");
                    }
                }

                totalBytesRead += bytesRead;
                if (totalBytesRead == dataLength)
                    break;
            }
            return dataBuffer;
        }

        /// <summary>
        /// 获取流数据
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="dataLength">要获取的长度</param>
        /// <param name="timeOutMillSeconds">超时时间</param>
        /// <returns></returns>
        /// <exception cref="TimeoutException"></exception>
        public static async Task<byte[]> ReadBytesAsync(this NetworkStream stream, int dataLength, CancellationToken token, int timeOutMillSeconds = 0)
        {
            // 用于存储接收到的数据
            byte[] dataBuffer = new byte[dataLength];
            int totalBytesRead = 0;
            int bytesRead = 0;
            var time = DateTime.Now;
            // 循环接收数据，直到接收到指定长度的数据
            while (true)
            {
                bytesRead = await stream.ReadAsync(dataBuffer, totalBytesRead, dataLength - totalBytesRead, token);
                if (bytesRead == 0)
                    break;

                if (timeOutMillSeconds > 0)
                {
                    var curr = DateTime.Now;
                    var span = curr - time;
                    if (span.TotalMilliseconds >= timeOutMillSeconds)
                    {
                        //stream.Close();
                        throw new TimeoutException("等待服务端返回超时");
                    }
                }

                totalBytesRead += bytesRead;
                if (totalBytesRead == dataLength)
                    break;
            }
            return dataBuffer;
        }

        /// <summary>
        /// 获取下一个(或当前)可用的端口号
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static int GetCurrentOrNextPort(this int port)
        {
            //check port ,more deivce can connect
            for (var i = port; i <= 65535; i++)
            {
                if (!PortInUse(i))
                {
                    return i;
                }
            }
            //This is a nonsense
            throw new Exception("No port can use");
        }

        /// <summary>
        /// 检测当前端口号是否被占用
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public static bool PortInUse(this int port)
        {
            var ipPorperties = IPGlobalProperties.GetIPGlobalProperties();
            System.Net.IPEndPoint[] ipEndPoints = ipPorperties.GetActiveTcpListeners();
            System.Net.IPEndPoint first = ipEndPoints.FirstOrDefault(i => i.Port == port);
            if (first != null)
                return true;

            ipEndPoints = ipPorperties.GetActiveUdpListeners();
            first = ipEndPoints.FirstOrDefault(i => i.Port == port);
            return first != null;
        }
        #endregion Public 方法
    }
}
